Python'ning unittest.mock kutubxonasini o'zlashtiring. Test dubllari, mock obyektlari, stublar, feklar va patch dekoratori haqida chuqur tahlil. Mustahkam va izolyatsiyalangan unit testlar uchun.
Python Mock obyektlari: Test Double'ni amalga oshirish bo'yicha keng qamrovli qo'llanma
Zamonaviy dasturiy ta'minotni ishlab chiqish dunyosida kod yozish jangning yarmi xolos. Kodning ishonchli, mustahkam va kutilganidek ishlashini ta'minlash esa boshqa, birdek muhim yarmi. Aynan shu yerda avtomatlashtirilgan testlash yordamga keladi. Xususan, unit testlash - bu dasturning alohida komponentlarini yoki 'bloklarini' izolyatsiya qilingan holda sinovdan o'tkazishni o'z ichiga olgan asosiy amaliyotdir. Biroq, bu izolyatsiyani amalga oshirish ko'pincha aytishdan osonroq. Haqiqiy dunyo ilovalari o'zaro bog'langan obyektlar, xizmatlar va tashqi tizimlarning murakkab to'plamidir. Agar bitta funksiya ma'lumotlar bazasiga, uchinchi tomon API'siga yoki tizimingizning boshqa murakkab qismiga bog'liq bo'lsa, uni qanday qilib sinovdan o'tkazasiz?
Javob kuchli texnikada yotadi: Test Doubles'dan foydalanish. Va Python ekotizimida ularni yaratish uchun asosiy vosita ko'p qirrali va ajralmas unittest.mock kutubxonasidir. Ushbu qo'llanma sizni Python'dagi mocklar va test dubllari dunyosiga chuqur olib kiradi. Biz ularning 'nima uchun'ligini o'rganamiz, turli xil turlarini tushuntiramiz va toza, tezroq va samaraliroq testlar yozishga yordam berish uchun unittest.mock'dan foydalangan holda amaliy, real dunyo misollarini taqdim etamiz.
Test Double'lar nima va ular bizga nima uchun kerak?
Tasavvur qiling, siz kompaniyangizning ma'lumotlar bazasidan foydalanuvchi profilini oladigan va keyin uni formatlaydigan funksiya yaratmoqdasiz. Funksiya imzosi quyidagicha ko'rinishga ega bo'lishi mumkin: get_formatted_user_profile(user_id, db_connection).
Ushbu funksiyani sinovdan o'tkazish uchun siz bir nechta qiyinchiliklarga duch kelasiz:
- Jonli tizimga bog'liqlik: Sizning testingiz ishlayotgan ma'lumotlar bazasini talab qiladi. Bu testlarni sekin, sozlashni murakkab va tashqi tizimning holati va mavjudligiga bog'liq qiladi.
- Bashorat qilib bo'lmaslik: Ma'lumotlar bazasidagi ma'lumotlar o'zgarishi mumkin, bu sizning formatlash mantig'ingiz to'g'ri bo'lsa ham testingizning muvaffaqiyatsiz bo'lishiga olib keladi. Bu testlarni 'noturg'un' yoki nodeterministik qiladi.
- Chekka holatlarni sinovdan o'tkazish qiyinligi: Agar ma'lumotlar bazasi ulanishi muvaffaqiyatsiz bo'lsa yoki ba'zi ma'lumotlari yo'q foydalanuvchini qaytarsa nima bo'lishini qanday sinovdan o'tkazasiz? Bu o'ziga xos stsenariylarni haqiqiy ma'lumotlar bazasi bilan simulyatsiya qilish juda qiyin bo'lishi mumkin.
Test Double - bu test davomida haqiqiy obyekt o'rnini bosuvchi har qanday obyekt uchun umumiy atama. Haqiqiy db_connection'ni test double bilan almashtirib, biz haqiqiy ma'lumotlar bazasiga bog'liqlikni uzishimiz va test muhitini to'liq nazorat qilishimiz mumkin.
Test dubllaridan foydalanish bir qancha asosiy afzalliklarni beradi:
- Izolyatsiya: Ular sizga kodingiz blokini (masalan, formatlash mantig'ini) uning bog'liqliklaridan (masalan, ma'lumotlar bazasi) to'liq izolyatsiyada sinovdan o'tkazishga imkon beradi. Agar test muvaffaqiyatsiz bo'lsa, muammo sinovdan o'tayotgan blokda ekanligini, boshqa joyda emasligini bilasiz.
- Tezlik: Tarmoq so'rovlari yoki ma'lumotlar bazasi so'rovlari kabi sekin operatsiyalarni xotiradagi test double bilan almashtirish test paketingizni sezilarli darajada tezroq ishga tushiradi. Tez testlar ko'proq ishga tushiriladi, bu esa dasturchilar uchun yanada qattiqroq fikr-mulohazalar aylanishiga olib keladi.
- Determinizm: Test double'ni har safar test ishga tushirilganda bashorat qilinadigan ma'lumotlarni qaytarishga sozlash mumkin. Bu noturg'un testlarni yo'q qiladi va muvaffaqiyatsiz test haqiqiy muammoni ko'rsatishini ta'minlaydi.
- Chekka holatlarni sinash imkoniyati: Xatolik sharoitlarini, masalan,
ConnectionErrorko'tarish yoki bo'sh ma'lumotlarni qaytarishni simulyatsiya qilish uchun double'ni osongina sozlash mumkin, bu sizning kodingiz ushbu vaziyatlarni to'g'ri boshqarishini tekshirishga imkon beradi.
Test Double'larning taksonomiyasi: "Mocklar"dan ham ortiq
Dasturchilar ko'pincha "mock" atamasini har qanday test double'ga nisbatan umumiy ma'noda ishlatishsa-da, Gerard Meszarosning "xUnit Test Patterns" kitobida kiritilgan aniqroq terminologiyani tushunish foydali bo'ladi. Bu farqlarni bilish testingizda nimaga erishmoqchi ekanligingiz haqida aniqroq fikr yuritishga yordam beradi.
1. Dummy
Dummy obyekt - bu eng oddiy test double. U parametrlar ro'yxatini to'ldirish uchun uzatiladi, lekin hech qachon haqiqatda ishlatilmaydi. Uning metodlari odatda chaqirilmaydi. Agar siz metodga argument berishingiz kerak bo'lsa-yu, lekin o'sha argumentning o'ziga xos test kontekstida qanday ishlashi sizga qiziq bo'lmasa, dummy'dan foydalanasiz.
Misol: Agar funksiya 'logger' obyektini talab qilsa, lekin sizning testingiz nimani yozib olish bilan bog'liq bo'lmasa, siz dummy obyektni uzatishingiz mumkin.
2. Fake
Fake obyektning ishchi implementatsiyasi mavjud, ammo bu ishlab chiqarish obyektining ancha soddaroq versiyasidir. U tashqi resurslardan foydalanmaydi va og'ir vaznli implementatsiyani yengilroq implementatsiya bilan almashtiradi. Klassik misol – haqiqiy ma'lumotlar bazasi ulanishini almashtiruvchi xotiradagi ma'lumotlar bazasi. U haqiqatan ham ishlaydi – unga ma'lumot qo'shishingiz va undan ma'lumot o'qishingiz mumkin – lekin u shunchaki oddiy lug'at yoki ro'yxatdir.
3. Stub
Stub test davomida metod chaqiruvlariga oldindan dasturlashtirilgan, "konservalangan" javoblarni taqdim etadi. U sizning kodingiz bog'liqlikdan aniq ma'lumotlarni qabul qilishi kerak bo'lganda ishlatiladi. Masalan, siz api_client.get_user(user_id=123) kabi metodni haqiqiy API chaqiruvini qilmasdan, har doim ma'lum bir foydalanuvchi lug'atini qaytarishga sozlashishingiz mumkin.
4. Spy
Spy - bu qanday chaqirilgani haqida ba'zi ma'lumotlarni yozib oluvchi stub. Masalan, u metodning necha marta chaqirilganini yoki unga qanday argumentlar uzatilganligini qayd etishi mumkin. Bu sizga kodingiz va uning bog'liqligi o'rtasidagi o'zaro ta'sirni "josuslik" qilishga va keyin bu o'zaro ta'sir haqida tasdiqlashlar kiritishga imkon beradi.
5. Mock
Mock - bu test double'ning eng 'xabardor' turi. Bu qaysi metodlar qaysi argumentlar bilan va qaysi tartibda chaqirilishi kutilayotgani oldindan dasturlashtirilgan obyekt. Mock obyektidan foydalanadigan test odatda sinovdan o'tayotgan kod noto'g'ri natija bersagina emas, balki mock bilan aniq kutilgan tarzda o'zaro ta'sir qilmasa ham muvaffaqiyatsiz bo'ladi. Mocklar xatti-harakatlarni tekshirish uchun juda yaxshi – ma'lum bir harakatlar ketma-ketligining ro'y berganligini ta'minlash.
Python'ning unittest.mock kutubxonasi undan qanday foydalanishingizga qarab Stub, Spy yoki Mock vazifasini bajarishi mumkin bo'lgan yagona, kuchli sinfni taqdim etadi.
Python'ning kuchli vositasini tanishtiramiz: `unittest.mock` kutubxonasi
Python 3.3 versiyasidan beri Python'ning standart kutubxonasining bir qismi bo'lgan unittest.mock test double'larni yaratish uchun kanonik yechimdir. Uning moslashuvchanligi va kuchi uni har qanday jiddiy Python dasturchisi uchun muhim vositaga aylantiradi. Agar siz Python'ning eski versiyasidan foydalanayotgan bo'lsangiz, backported kutubxonani pip orqali o'rnatishingiz mumkin: pip install mock.
Kutubxonaning yadrosi ikkita asosiy sinfga asoslangan: Mock va uning qobiliyatli ukasi, MagicMock. Bu obyektlar nihoyatda moslashuvchan bo'lishi uchun mo'ljallangan, ularga kirish paytida atributlar va metodlarni tezda yaratadi.
Chuqur tahlil: `Mock` va `MagicMock` sinflari
`Mock` obyekti
`Mock` obyekti bu xameleon. Siz uni yaratishingiz mumkin va u har qanday atributga kirish yoki metod chaqiruviga darhol javob beradi, sukut bo'yicha boshqa Mock obyektini qaytaradi. Bu sozlash paytida chaqiruvlarni osongina zanjirlash imkonini beradi.
# In a test file...
from unittest.mock import Mock
# Create a mock object
mock_api = Mock()
# Accessing an attribute creates it and returns another mock
print(mock_api.users)
# Output: <Mock name='mock.users' id='...'>
# Calling a method also returns a mock by default
print(mock_api.users.get(id=1))
# Output: <Mock name='mock.users.get()' id='...'>
Bu sukutdagi xatti-harakat testlash uchun unchalik foydali emas. Haqiqiy kuch mock'ni u almashtirayotgan obyekt kabi ishlashga sozlashdan kelib chiqadi.
Qaytarish qiymatlari va yon ta'sirlarni sozlash
Siz mock metodiga return_value atributidan foydalanib nimani qaytarishni aytishingiz mumkin. Bu Stub'ni qanday yaratishingizdir.
from unittest.mock import Mock
# Ma'lumotlar xizmati uchun mock yaratish
mock_service = Mock()
# Metod chaqiruvi uchun qaytarish qiymatini sozlash
mock_service.get_data.return_value = {'id': 1, 'name': 'Test Data'}
# Endi uni chaqirganimizda, biz sozlagan qiymatni olamiz
result = mock_service.get_data()
print(result)
# Output: {'id': 1, 'name': 'Test Data'}
Xatolarni simulyatsiya qilish uchun side_effect atributidan foydalanishingiz mumkin. Bu kodingizning xatolarni boshqarishini sinovdan o'tkazish uchun juda mos keladi.
from unittest.mock import Mock
mock_service = Mock()
# Metodni istisno ko'tarish uchun sozlash
mock_service.get_data.side_effect = ConnectionError("Xizmatga ulanish muvaffaqiyatsiz tugadi")
# Metodni chaqirish endi istisno ko'taradi
try:
mock_service.get_data()
except ConnectionError as e:
print(e)
# Output: Xizmatga ulanish muvaffaqiyatsiz tugadi
Tekshirish uchun tasdiqlash metodlari
Mock obyektlari qanday ishlatilganligini yozib olish orqali Spies va Mocks vazifasini ham bajaradi. Keyin siz bu o'zaro ta'sirlarni tasdiqlash uchun bir qator o'rnatilgan tasdiqlash metodlaridan foydalanishingiz mumkin.
mock_object.method.assert_called(): Metod kamida bir marta chaqirilganligini tasdiqlaydi.mock_object.method.assert_called_once(): Metod aynan bir marta chaqirilganligini tasdiqlaydi.mock_object.method.assert_called_with(*args, **kwargs): Metod oxirgi marta ko'rsatilgan argumentlar bilan chaqirilganligini tasdiqlaydi.mock_object.method.assert_any_call(*args, **kwargs): Metod istalgan paytda ushbu argumentlar bilan chaqirilganligini tasdiqlaydi.mock_object.method.assert_not_called(): Metod hech qachon chaqirilmaganligini tasdiqlaydi.mock_object.call_count: Metod necha marta chaqirilganligini ko'rsatuvchi butun son xususiyati.
from unittest.mock import Mock
mock_notifier = Mock()
# Tasavvur qiling, bu bizning sinovdan o'tayotgan funksiyamiz
def process_and_notify(data, notifier):
if data.get('critical'):
notifier.send_alert(message="Muhim hodisa yuz berdi!")
# Test holati 1: Muhim ma'lumotlar
process_and_notify({'critical': True}, mock_notifier)
mock_notifier.send_alert.assert_called_once_with(message="Muhim hodisa yuz berdi!")
# Keyingi test uchun mock'ni qayta tiklash
mock_notifier.reset_mock()
# Test holati 2: Muhim bo'lmagan ma'lumotlar
process_and_notify({'critical': False}, mock_notifier)
mock_notifier.send_alert.assert_not_called()
`MagicMock` obyekti
`MagicMock` bu `Mock` sinfining asosiy farqi bo'lgan kichik sinfi: u Python'ning ko'pgina "sehrli" yoki "dunder" metodlari (masalan, __len__, __str__, __iter__) uchun standart implementatsiyalarga ega. Agar siz ushbu metodlardan birini talab qiladigan kontekstda oddiy `Mock`'dan foydalanmoqchi bo'lsangiz, xatolik olasiz.
from unittest.mock import Mock, MagicMock
# Oddiy Mock'dan foydalanish
mock_list = Mock()
try:
len(mock_list)
except TypeError as e:
print(e) # Output: 'Mock' obyekti len() metodiga ega emas
# MagicMock'dan foydalanish
magic_mock_list = MagicMock()
print(len(magic_mock_list)) # Output: 0 (sukut bo'yicha)
# Sehrli metodning qaytarish qiymatini ham sozlashimiz mumkin
magic_mock_list.__len__.return_value = 100
print(len(magic_mock_list)) # Output: 100
Umumiy qoida: `MagicMock` bilan boshlang. Bu odatda xavfsizroq va ko'proq foydalanish holatlarini qamrab oladi, masalan, for sikllarida (__iter__ talab qiladi) yoki with operatorlarida (__enter__ va __exit__ talab qiladi) ishlatiladigan obyektlarni mock qilish.
Amaliy implementatsiya: `patch` dekoratori va kontekst menejeri
Mock yaratish bir ish, lekin kodingizni haqiqiy obyekt o'rniga undan foydalanishga qanday majburlaysiz? Aynan shu yerda `patch` yordamga keladi. `patch` - bu `unittest.mock`'dagi kuchli vosita bo'lib, test davomida maqsadli obyektni vaqtincha mock bilan almashtiradi.
`@patch` dekorator sifatida
`patch`'dan foydalanishning eng keng tarqalgan usuli uni test metodida dekorator sifatida ishlatishdir. Siz almashtirmoqchi bo'lgan obyektga satr yo'lini ko'rsatasiz.
Aytaylik, bizda mashhur `requests` kutubxonasidan foydalanib veb-API'dan ma'lumotlarni oladigan funksiya bor:
# faylda: my_app/data_fetcher.py
import requests
def get_user_data(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status() # Yomon status kodlari uchun istisno ko'tarish
return response.json()
Biz ushbu funksiyani haqiqiy tarmoq chaqiruvini qilmasdan sinovdan o'tkazishni istaymiz. Biz `requests.get`'ni patch qilishimiz mumkin:
# faylda: tests/test_data_fetcher.py
import unittest
from unittest.mock import patch, Mock
from my_app.data_fetcher import get_user_data
class TestDataFetcher(unittest.TestCase):
@patch('my_app.data_fetcher.requests.get')
def test_get_user_data_success(self, mock_get):
"""Ma'lumotlarni muvaffaqiyatli olishni sinovdan o'tkazish."""
# Mock'ni muvaffaqiyatli API javobini simulyatsiya qilish uchun sozlash
mock_response = Mock()
mock_response.json.return_value = {'id': 1, 'name': 'John Doe'}
mock_response.raise_for_status.return_value = None # Muvaffaqiyatli bo'lganda hech narsa qilmaslik
mock_get.return_value = mock_response
# Bizning funksiyamizni chaqirish
user_data = get_user_data(1)
# Bizning funksiyamiz to'g'ri API chaqiruvini bajarganligini tasdiqlash
mock_get.assert_called_once_with('https://api.example.com/users/1')
# Bizning funksiyamiz kutilgan ma'lumotlarni qaytarganligini tasdiqlash
self.assertEqual(user_data, {'id': 1, 'name': 'John Doe'})
`patch` qanday qilib `MagicMock` yaratishini va uni test metodimizga `mock_get` argumenti sifatida uzatishini ko'ring. Test ichida `my_app.data_fetcher` ichidagi `requests.get` ga har qanday chaqiruv bizning mock obyektimizga yo'naltiriladi.
`patch` kontekst menejeri sifatida
Ba'zan sizga faqat testning kichik bir qismi uchun nimadir patch qilish kerak bo'ladi. `patch`'ni `with` operatori bilan kontekst menejeri sifatida ishlatish buning uchun juda mos keladi.
# faylda: tests/test_data_fetcher.py
import unittest
from unittest.mock import patch, Mock
from my_app.data_fetcher import get_user_data
class TestDataFetcher(unittest.TestCase):
def test_get_user_data_with_context_manager(self):
"""patch'ni kontekst menejeri sifatida ishlatishni sinovdan o'tkazish."""
with patch('my_app.data_fetcher.requests.get') as mock_get:
# 'with' bloki ichida mock'ni sozlash
mock_response = Mock()
mock_response.json.return_value = {'id': 2, 'name': 'Jane Doe'}
mock_get.return_value = mock_response
user_data = get_user_data(2)
mock_get.assert_called_once_with('https://api.example.com/users/2')
self.assertEqual(user_data, {'id': 2, 'name': 'Jane Doe'})
# 'with' blokidan tashqarida, requests.get o'zining asl holatiga qaytadi
Hal qiluvchi tushuncha: Qayerga patch qilish kerak?
Bu `patch`'dan foydalanishda eng keng tarqalgan chalkashlik manbai. Qoida quyidagicha: Siz obyektni u qayerdan qidirilayotgan bo'lsa, o'sha joyda patch qilishingiz kerak, u qayerda aniqlanganligida emas.
Misol bilan tushuntiramiz. Aytaylik, bizda ikkita fayl bor:
# faylda: services.py
class Database:
def connect(self):
# ... murakkab ulanish mantig'i ...
return "REAL_CONNECTION"
# faylda: main_app.py
from services import Database
def start_app():
db = Database()
connection = db.connect()
print(f"Ulanish olindi: {connection}")
return connection
Endi biz `main_app.py` dagi `start_app`'ni haqiqiy `Database` obyektini yaratmasdan sinovdan o'tkazmoqchimiz. Keng tarqalgan xato `services.Database`'ni patch qilishga urinishdir.
# faylda: test_main_app.py
import unittest
from unittest.mock import patch
from main_app import start_app
class TestApp(unittest.TestCase):
# BU PATCH QILISHNING Noto'g'ri USULI!
@patch('services.Database')
def test_start_app_incorrectly(self, mock_db):
start_app()
# Bu test hali ham HAQIQIY Database sinfidan foydalanadi!
# BU PATCH QILISHNING To'g'ri USULI!
@patch('main_app.Database')
def test_start_app_correctly(self, mock_db_class):
# Biz 'Database'ni 'main_app' nomlar fazosida patch qilmoqdamiz
# Yaratiladigan mock instansiyasini sozlash
mock_instance = mock_db_class.return_value
mock_instance.connect.return_value = "MOCKED_CONNECTION"
connection = start_app()
# Mockimiz ishlatilganligini tasdiqlash
mock_db_class.assert_called_once() # Sinf instansiyalanganmi?
mock_instance.connect.assert_called_once() # Ulanish metodi chaqirilganmi?
self.assertEqual(connection, "MOCKED_CONNECTION")
Nima uchun birinchi test muvaffaqiyatsiz tugaydi? Chunki `main_app.py` `from services import Database` ni bajaradi. Bu `Database` sinfini `main_app` modulining nomlar fazosiga import qiladi. `start_app` ishga tushganda, u o'z modulida (`main_app`) `Database` ni qidiradi. `services.Database` ni patch qilish uni `services` modulida o'zgartiradi, ammo `main_app` allaqachon asl sinfga o'z havolaiga ega. To'g'ri yondashuv `main_app.Database` ni patch qilishdir, bu sinovdan o'tayotgan kod haqiqatan ham foydalanadigan nomdir.
Ilg'or Mocklash texnikalari
`spec` va `autospec`: Mock'larni xavfsizroq qilish
Standart `MagicMock` ning potentsial kamchiligi bor: u har qanday metodni har qanday argumentlar bilan chaqirishga imkon beradi, hatto bu metod haqiqiy obyektda mavjud bo'lmasa ham. Bu o'tadigan, ammo metod nomlaridagi imlo xatolari yoki haqiqiy obyekt API'sining o'zgarishi kabi haqiqiy muammolarni yashiradigan testlarga olib kelishi mumkin.
# Haqiqiy sinf
class Notifier:
def send_message(self, text):
# ... xabar yuboradi ...
pass
# Imlo xatosi bilan test
from unittest.mock import MagicMock
mock_notifier = MagicMock()
# Voy, imlo xatosi! Haqiqiy metod send_message
mock_notifier.send_mesage("hello") # Xatolik ko'tarilmaydi!
mock_notifier.send_mesage.assert_called_with("hello") # Bu tasdiqlash o'tadi!
# Testimiz yashil, lekin ishlab chiqarish kodi ishlamay qoladi.
Buni oldini olish uchun `unittest.mock` `spec` va `autospec` argumentlarini taqdim etadi.
- `spec=SomeClass`: Bu mock'ni `SomeClass` bilan bir xil API'ga ega bo'lishiga sozlaydi. Agar siz haqiqiy sinfda mavjud bo'lmagan metod yoki atributga kirishga harakat qilsangiz, `AttributeError` ko'tariladi.
- `autospec=True` (yoki `autospec=SomeClass`): Bu undan ham kuchliroq. U `spec` kabi ishlaydi, ammo u har qanday mock qilingan metodlarning chaqiruv imzosini ham tekshiradi. Agar siz metodni noto'g'ri son yoki argument nomlari bilan chaqirsangiz, u haqiqiy obyekt singari `TypeError` ko'taradi.
from unittest.mock import create_autospec
# Notifier sinfimiz bilan bir xil interfeysga ega mock yaratish
spec_notifier = create_autospec(Notifier)
try:
# Bu imlo xatosi tufayli darhol ishlamay qoladi
spec_notifier.send_mesage("hello")
except AttributeError as e:
print(e) # Output: Mock obyektida 'send_mesage' atributi yo'q
try:
# Bu imzo noto'g'ri bo'lganligi sababli (text kalit so'zi yo'q) ishlamay qoladi
spec_notifier.send_message("hello")
except TypeError as e:
print(e) # Output: 'text' nomli kerakli argument yo'q
# Bu uni chaqirishning to'g'ri usuli
spec_notifier.send_message(text="hello") # Bu ishlaydi!
spec_notifier.send_message.assert_called_once_with(text="hello")
Eng yaxshi amaliyot: Patch qilishda har doim `autospec=True` dan foydalaning. Bu sizning testlaringizni mustahkamroq va kamroq zaif qiladi. `@patch('path.to.thing', autospec=True)`.
Haqiqiy dunyo misoli: Ma'lumotlarni qayta ishlash xizmatini sinovdan o'tkazish
Keling, hamma narsani yanada to'liqroq misol bilan birlashtiramiz. Bizda ma'lumotlar bazasi va fayl tizimiga bog'liq bo'lgan `ReportGenerator` mavjud.
# faylda: app/services.py
class DatabaseConnector:
def get_sales_data(self, start_date, end_date):
# Aslida, bu ma'lumotlar bazasini so'rov qiladi
raise NotImplementedError("Bu testlarda chaqirilmasligi kerak")
class FileSaver:
def save_report(self, path, content):
# Aslida, bu faylga yozadi
raise NotImplementedError("Bu testlarda chaqirilmasligi kerak")
# faylda: app/reports.py
from .services import DatabaseConnector, FileSaver
class ReportGenerator:
def __init__(self):
self.db_connector = DatabaseConnector()
self.file_saver = FileSaver()
def generate_sales_report(self, start_date, end_date, output_path):
"""Savdo ma'lumotlarini oladi va formatlangan hisobotni saqlaydi."""
raw_data = self.db_connector.get_sales_data(start_date, end_date)
if not raw_data:
report_content = "Ushbu davr uchun savdo ma'lumotlari yo'q."
else:
total_sales = sum(item['amount'] for item in raw_data)
report_content = f"Jami savdolar {start_date} dan {end_date} gacha: ${total_sales:.2f}"
self.file_saver.save_report(path=output_path, content=report_content)
return True
Endi `ReportGenerator.generate_sales_report` uchun uning bog'liqliklarini mock qiluvchi unit test yozaylik.
# faylda: tests/test_reports.py
import unittest
from datetime import date
from unittest.mock import patch, Mock
from app.reports import ReportGenerator
class TestReportGenerator(unittest.TestCase):
@patch('app.reports.FileSaver', autospec=True)
@patch('app.reports.DatabaseConnector', autospec=True)
def test_generate_sales_report_with_data(self, mock_db_connector_class, mock_file_saver_class):
"""Ma'lumotlar bazasi ma'lumotlarni qaytarganida hisobot yaratilishini sinovdan o'tkazish."""
# Arrange: Mocklarimizni sozlash
mock_db_instance = mock_db_connector_class.return_value
mock_file_saver_instance = mock_file_saver_class.return_value
# Ma'lumotlar bazasi mock'ini soxta ma'lumotlarni qaytarishga sozlash (Stub)
fake_data = [
{'id': 1, 'amount': 100.50},
{'id': 2, 'amount': 75.00},
{'id': 3, 'amount': 25.25}
]
mock_db_instance.get_sales_data.return_value = fake_data
start = date(2023, 1, 1)
end = date(2023, 1, 31)
path = '/reports/sales_jan_2023.txt'
# Act: Sinfimiz instansiyasini yaratish va metodni chaqirish
generator = ReportGenerator()
result = generator.generate_sales_report(start, end, path)
# Assert: O'zaro ta'sirlar va natijalarni tekshirish
# 1. Ma'lumotlar bazasi to'g'ri chaqirilganmi?
mock_db_instance.get_sales_data.assert_called_once_with(start, end)
# 2. Fayl saqlovchi to'g'ri, hisoblangan kontent bilan chaqirilganmi?
expected_content = "Jami savdolar 2023-01-01 dan 2023-01-31 gacha: $200.75"
mock_file_saver_instance.save_report.assert_called_once_with(
path=path,
content=expected_content
)
# 3. Bizning metodimiz to'g'ri qiymatni qaytarganmi?
self.assertTrue(result)
Bu test `generate_sales_report` ichidagi mantig'ni ma'lumotlar bazasi va fayl tizimining murakkabliklaridan mukammal izolyatsiya qiladi, shu bilan birga u ular bilan to'g'ri o'zaro ta'sir qilishini tekshiradi.
Samarali Mocklash uchun eng yaxshi amaliyotlar
- Mock'larni sodda tuting: Juda murakkab mock konfiguratsiyasini talab qiladigan test ko'pincha sinovdan o'tayotgan blokning juda murakkab ekanligi va Yagona Mas'uliyat Prinsipini buzayotganligining belgisi ("test hidi") hisoblanadi. Ishlab chiqarish kodini qayta tuzishni ko'rib chiqing.
- Hamkorlarni mock qiling, hamma narsani emas: Siz faqat sinovdan o'tayotgan blokingiz bilan aloqa qiladigan obyektlarni (uning hamkorlarini) mock qilishingiz kerak. O'zingiz sinovdan o'tkazayotgan obyektni mock qilmang.
- `autospec=True`'ni afzal ko'ring: Aytib o'tilganidek, bu mock interfeysi haqiqiy obyekt interfeysiga mos kelishini ta'minlab, testlaringizni yanada mustahkam qiladi. Bu qayta tuzish natijasida yuzaga keladigan muammolarni aniqlashga yordam beradi.
- Har bir test uchun bitta mock (ideal holda): Yaxshi unit test bitta xatti-harakat yoki o'zaro ta'sirga e'tibor qaratadi. Agar siz bitta testda ko'plab turli obyektlarni mock qilayotganingizni topsangiz, uni bir nechta, aniqroq testlarga ajratish yaxshiroq bo'lishi mumkin.
- Tasdiqlashlaringizda aniq bo'ling: Faqat `mock.method.assert_called()` ni tekshirmang. O'zaro ta'sir to'g'ri ma'lumotlar bilan sodir bo'lganligini ta'minlash uchun `assert_called_with(...)` dan foydalaning. Bu testlaringizni yanada qimmatli qiladi.
- Sizning testlaringiz hujjatdir: Testlaringiz va mock obyektlaringiz uchun aniq va tushunarli nomlardan foydalaning (masalan, `mock_api_client`, `test_login_fails_on_network_error`). Bu testning maqsadini boshqa dasturchilar uchun aniq qiladi.
Xulosa
Test double'lar shunchaki testlash vositasi emas; ular sinovdan o'tkazilishi mumkin, modulli va oson saqlanadigan dasturiy ta'minotni loyihalashning fundamental qismidir. Haqiqiy bog'liqliklarni nazorat qilinadigan o'rnini bosuvchilar bilan almashtirib, siz tez, ishonchli va dasturingiz mantig'ining har bir burchagini tekshirishga qodir test to'plamini yaratishingiz mumkin.
Python'ning unittest.mock kutubxonasi ushbu shablonlarni amalga oshirish uchun jahon darajasidagi vositalar to'plamini taqdim etadi. MagicMock, `patch` va `autospec` xavfsizligini o'zlashtirish orqali siz chinakam izolyatsiyalangan unit testlarni yozish qobiliyatini ochasiz. Bu sizga murakkab ilovalarni ishonch bilan yaratishga imkon beradi, chunki sizda regressiyalarni ushlash va yangi xususiyatlarni tasdiqlash uchun aniq, maqsadli testlarning xavfsizlik tarmog'i borligini bilasiz. Shunday qilib, davom eting, patch qilishni boshlang va bugun yanada mustahkam Python ilovalarini yarating.